import { runInAction } from "mobx";
import React, { useCallback } from "react";
import type { DivEvent } from "../../typings";
import JoyStickConfig from "./config";
import { emptyImg } from "@/assets/empty-img";
import useAppStore from "@/store/hooks";
import { Store } from "@/store/store";
import throttle from "@/utils/throttle";
import useTicker from "@/hooks/useTicker";
import ViewConfig from "@/views/config";
import styles from "./style.module.scss";

/**
 * @className joystick
 */
const Joystick: React.FC = () => {
  const { site, hero, fps, joyStick } = useAppStore("store");
  const { isDragging } = joyStick;
  const setIsDragging = (v: boolean) => {
    isDragging !== v && (joyStick.isDragging = v);
  };

  const updateSite = useCallback(
    throttle(
      // @ts-ignore
      function (offsetX: number, offsetY: number, site: Store["site"]) {
        if (!joyStick.isDragging) return;
        const speed = (((10 * 30) / fps) * hero.speed) / 100;
        const speedX = (speed * Math.abs(joyStick.x)) / 60;
        const speedY = (speed * Math.abs(joyStick.y)) / 60;
        Promise.all([
          new Promise<number>((resolve) => {
            let x = site.x;
            if (offsetX > 0) {
              x = Math.min(
                site.x + speedX,
                ViewConfig.stageWidth / 2 - ViewConfig.heroWidth / 2
              );
            } else if (offsetX < 0) {
              x = Math.max(
                site.x - speedX,
                (-1 * ViewConfig.stageWidth) / 2 + ViewConfig.heroWidth / 2
              );
            }
            x = parseInt(x.toString());
            if (x !== site.x) {
              runInAction(() => {
                site.x = x;
              });
            }
            resolve(x);
          }),
          new Promise<number>((resolve) => {
            let y = site.y;
            if (offsetY > 0) {
              y = Math.min(
                site.y + speedY,
                ViewConfig.stageHeight / 2 - ViewConfig.heroHeight / 2
              );
            } else if (offsetY < 0) {
              y = Math.max(
                site.y - speedY,
                (-1 * ViewConfig.stageHeight) / 2 + ViewConfig.heroHeight / 2
              );
            }
            y = parseInt(y.toString());
            if (y !== site.y) {
              runInAction(() => {
                site.y = y;
              });
            }
            resolve(y);
          }),
        ]).then(([x, y]) => {
          // console.log("site", `X: ${x}, Y: ${y}`);
        });
      },
      1000 / fps
    ),
    [fps]
  );

  useTicker(
    () => {
      updateSite(joyStick.x, -1 * joyStick.y, site);
    },
    0,
    {
      immediate: true,
      callAtFirst: true,
    }
  );

  const handleDragEnter: DivEvent["onDragEnter"] = (e) => {
    e.preventDefault();
  };

  const handleDragStart: DivEvent["onDragStart"] = (e) => {
    e.nativeEvent.dataTransfer?.setDragImage(emptyImg, 0, 0);
    if (e.nativeEvent.dataTransfer?.effectAllowed) {
      e.nativeEvent.dataTransfer.effectAllowed = "all";
    }
    if (joyStick.dragglable) {
      setIsDragging(true);
    }
  };

  const handleTouchStart: DivEvent["onTouchStart"] = (e) => {
    if (joyStick.dragglable) {
      setIsDragging(true);
    }
  };

  const handleDragging: DivEvent["onMouseMove"] = (e) => {
    if (!isDragging) return;
    const rect = e.currentTarget.getBoundingClientRect();
    let x = e.clientX - rect.left - rect.width / 2;
    let y = e.clientY - rect.top - rect.height / 2;

    // 计算鼠标位置与摇杆中心的距离
    const distance = Math.sqrt(x * x + y * y);

    // 如果距离大于摇杆的半径，则限制摇杆的移动范围
    const radiusAllowed = JoyStickConfig.radius + JoyStickConfig.pointerRadius;
    if (distance > radiusAllowed) {
      x = (x / distance) * radiusAllowed;
      y = (y / distance) * radiusAllowed;
    }

    // 无论鼠标的移动距离是否超过摇杆的半径，都更新摇杆的位置
    joyStick.x = x;
    joyStick.y = -y;
  };

  const handleTouchMove: DivEvent["onTouchMove"] = (e) => {
    if (!isDragging) return;
    const rect = e.currentTarget.getBoundingClientRect();
    let x = e.touches[0].clientX - rect.left - rect.width / 2;
    let y = e.touches[0].clientY - rect.top - rect.height / 2;

    // 计算鼠标位置与摇杆中心的距离
    const distance = Math.sqrt(x * x + y * y);

    // 如果距离大于摇杆的半径，则限制摇杆的移动范围
    const radiusAllowed = JoyStickConfig.radius + JoyStickConfig.pointerRadius;
    if (distance > radiusAllowed) {
      x = (x / distance) * radiusAllowed;
      y = (y / distance) * radiusAllowed;
    }

    // 无论鼠标的移动距离是否超过摇杆的半径，都更新摇杆的位置
    joyStick.x = x;
    joyStick.y = -y;
  };

  const handleDragEnd = () => {
    setIsDragging(false);
    joyStick.x = 0;
    joyStick.y = 0;
  };

  const handleTouchEnd = () => {
    setIsDragging(false);
    joyStick.x = 0;
    joyStick.y = 0;
  };

  const handleDragOver: DivEvent["onDragOver"] = (e) => {
    e.preventDefault();
    if (e.nativeEvent.dataTransfer?.dropEffect) {
      e.dataTransfer.dropEffect = "move";
    }
  };

  const [keysPressed, setKeysPressed] = React.useState({
    w: false,
    a: false,
    s: false,
    d: false,
  });

  const updateJoyStickPosition = useCallback((keys: typeof keysPressed) => {
    const isWOrS = keys.w !== keys.s;
    const isAOrD = keys.a !== keys.d;

    if (keys.w && keys.a && keys.s && keys.d) {
      joyStick.x = 0;
      joyStick.y = 0;
    } else if (isWOrS && isAOrD) {
      if (keys.w && keys.d) {
        joyStick.x = joyStick.y = JoyStickConfig.radius * Math.cos(Math.PI / 4);
      } else if (keys.w && keys.a) {
        joyStick.x = -JoyStickConfig.radius * Math.cos(Math.PI / 4);
        joyStick.y = JoyStickConfig.radius * Math.cos(Math.PI / 4);
      } else if (keys.s && keys.a) {
        joyStick.x = joyStick.y =
          -JoyStickConfig.radius * Math.cos(Math.PI / 4);
      } else if (keys.s && keys.d) {
        joyStick.x = JoyStickConfig.radius * Math.cos(Math.PI / 4);
        joyStick.y = -JoyStickConfig.radius * Math.cos(Math.PI / 4);
      }
    } else {
      if (isWOrS) {
        if (keys.w) {
          joyStick.y = JoyStickConfig.radius;
        } else {
          joyStick.y = -JoyStickConfig.radius;
        }
      } else {
        joyStick.y = 0;
      }
      if (isAOrD) {
        if (keys.a) {
          joyStick.x = -JoyStickConfig.radius;
        } else {
          joyStick.x = JoyStickConfig.radius;
        }
      } else {
        joyStick.x = 0;
      }
    }
  }, []);

  const handleKeyDown = useCallback(
    (event: KeyboardEvent) => {
      if (!joyStick.dragglable) {
        return;
      }
      const Key = event.key.toLowerCase();
      setKeysPressed((keys) => {
        const newKeys = { ...keys, [Key]: true };
        joyStick.isDragging = true;
        updateJoyStickPosition(newKeys);
        return newKeys;
      });
    },
    [updateJoyStickPosition]
  );

  const handleKeyUp = useCallback(
    (event: KeyboardEvent) => {
      const Key = event.key.toLowerCase();
      setKeysPressed((keys) => {
        const newKeys = { ...keys, [Key]: false };
        if (
          !["w", "a", "s", "d"].some(
            (key) => newKeys[key as keyof typeof newKeys]
          )
        ) {
          joyStick.isDragging = false;
          joyStick.x = 0;
          joyStick.y = 0;
        } else {
          updateJoyStickPosition(newKeys);
        }
        return newKeys;
      });
    },
    [updateJoyStickPosition]
  );

  React.useEffect(() => {
    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);

    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [handleKeyDown, handleKeyUp, keysPressed]);

  return (
    <div
      className={styles.joystick}
      onDragEnter={handleDragEnter}
      onDragStart={handleDragStart}
      onDrag={handleDragging}
      onDragEnd={handleDragEnd}
      onDragOver={handleDragOver}
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
    >
      <div
        className={styles.joystick__pointer}
        draggable={joyStick.dragglable}
        style={{
          position: "relative",
          top: `calc(50% - ${joyStick.y}px)`,
          left: `calc(50% + ${joyStick.x}px)`,
          width: `${2 * JoyStickConfig.pointerRadius}px`,
          height: `${2 * JoyStickConfig.pointerRadius}px`,
          background: `${JoyStickConfig.pointerColor}`,
          borderRadius: "50%",
          transform: "translate(-50%, -50%)",
        }}
      />
    </div>
  );
};

export default Joystick;
